/****************************************************************************
 * FILE: test-main.c
 * AUTHOR: William Stafford Noble
 * CREATE DATE: 1/25/2000
 * PROJECT: SVM
 * COPYRIGHT: 1999-2001, Columbia University (see ../doc/copyright.html)
 * VERSION: $Revision: 1.36 $
 * DESCRIPTION: Main procedure for classifying or projecting.
 ****************************************************************************/
#include "kernel.h"
#include "classify.h"
#include "kernel-pca.h"
#include "project.h"
#include "compute-kernel.h"
#include "rdb-matrix.h"
#include "string-list.h"
#include "matrix.h"
#include "array.h"
#include "utils.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#ifndef CLASSIFY_DEBUG
#define CLASSIFY_DEBUG 0
#endif

#ifndef CLASSIFY
#define CLASSIFY 0
#endif

#ifndef PROJECT
#define PROJECT 0
#endif



/****************************************************************************
 * Read the kernel name and polynomial parameters from the header of a
 * given weights file.
 ****************************************************************************/
#define MAX_LINE 500
static void read_kernel_parameters
  (char*      learned_filename,
   KERNEL_T*  kernel)
{
  char  one_line[MAX_LINE];
  FILE* learned_file;
  int   num_scanned;
  char  matrix_from_file_string[MAX_LINE];
  char  zero_mean_string[MAX_LINE];
  char  variance_one_string[MAX_LINE];
  char  normalize_string[MAX_LINE];
  char  radial_string[MAX_LINE];
  char  feature_select_string[MAX_LINE];

  /* Open the weight file for reading. */
  if (!open_file(learned_filename, "r", TRUE, "weights", "weights",
		 &learned_file)) {
    exit(1);
  }

  // Read until we find the first line of parameters.
  num_scanned = 0;
  while (num_scanned != 4) {
    if (fgets(one_line, MAX_LINE, learned_file) == NULL) {
      die("Error parsing weight file header.\n%s", one_line);
    }
    num_scanned 
      = sscanf(one_line, 
	       "# matrix_from_file=%s zero_mean=%s variance_one=%s normalize=%s",
	       matrix_from_file_string, zero_mean_string, variance_one_string,
	       normalize_string);
  }
  kernel->matrix_from_file = boolean_from_string(matrix_from_file_string);
  kernel->zero_mean_row = boolean_from_string(zero_mean_string);
  kernel->variance_one = boolean_from_string(variance_one_string);
  kernel->normalize = boolean_from_string(normalize_string);

  /* Parse the fifth line. */
  fgets(one_line, MAX_LINE, learned_file);
  num_scanned 
    = sscanf(one_line, 
	     "# constant=%lf coefficient=%lf power=%lf",
	     &(kernel->constant), &(kernel->coefficient), &(kernel->power));
  if (num_scanned < 3) {
    die("Error parsing weight file header.\n%s", one_line);
  }

  /* Parse the sixth line. */
  fgets(one_line, MAX_LINE, learned_file);
  num_scanned 
    = sscanf(one_line, 
	     "# radial=%s width_factor=%lf two_squared_width=%lf add_diag=%lf",
	     radial_string, &(kernel->width_factor), &(kernel->two_squared_width), &(kernel->add_diag));
  if (num_scanned < 2) {
    die("Error parsing weight file header.\n%s", one_line);
  }
  /* Allow reading of old files that don't include add_diag. */
  if (num_scanned == 2) {
    kernel->add_diag = 0.0;
  }
  kernel->radial = boolean_from_string(radial_string);

  // Read feature selection parameters from the seventh line.
  fgets(one_line, MAX_LINE, learned_file);
  num_scanned = sscanf(one_line, "# feature_select=%s", feature_select_string);
  if (num_scanned == 1) {
    if (strcmp(feature_select_string, "none") != 0) {
      die("Cannot perform feature selection during classification.");
    }
  } else {
    // Allow compatibility with old files.
    //die("Error parsing weight file at line seven.");
  }

  /* Close the file. */
  fclose(learned_file);
}

/****************************************************************************
 * Put an RDB header on the output file.
 ****************************************************************************/
static void print_output_header
  (char*     learned_filename,
   KERNEL_T* kernel,
   BOOLEAN_T print_time,
   FILE*     outfile)
{
  if (CLASSIFY) {
    fprintf(outfile, "# Generated by classify\n");
  } else {
    fprintf(outfile, "# Generated by project\n");
  }

  // Print release, web site and citation info.
  fprintf(outfile, "# Gist, version %s\n", RELEASE);
  fprintf(outfile, "# For more information, go to http://svm.sdsc.edu\n# \n");
  // FIXME: Add cite.
  // fprintf(outfile, "# If you use this software in your research, please cite:\n");

  /* Print file names. */
  if (kernel->train_filename != NULL) {
    fprintf(outfile, "# train_file=%s\n", kernel->train_filename);
  }
  if (learned_filename != NULL) {
    fprintf(outfile, "# learned_file=%s\n", learned_filename);
  }
  if (kernel->test_filename != NULL) {
    fprintf(outfile, "# test_file=%s\n", kernel->test_filename);
  }
  if (kernel->self_train_filename != NULL) {
    fprintf(outfile, "# self_train_file=%s\n", kernel->self_train_filename);
  }
  if (kernel->self_test_filename != NULL) {
    fprintf(outfile, "# self_test_file=%s\n", kernel->self_test_filename);
  }

  /* Print the properties of the kernel. */
  fprintf(outfile, "# matrix_from_file=%s", 
	  boolean_to_string(kernel->matrix_from_file));
  fprintf(outfile, " zero_mean=%s", 
	  boolean_to_string(kernel->zero_mean_row));
  fprintf(outfile, " variance_one=%s",
	  boolean_to_string(kernel->variance_one));
  fprintf(outfile, " normalize=%s\n",
	  boolean_to_string(kernel->normalize));
  fprintf(outfile, "# constant=%g", kernel->constant);
  fprintf(outfile, " coefficient=%g", kernel->coefficient);
  fprintf(outfile, " power=%g\n", kernel->power);
  fprintf(outfile, "# radial=%s", boolean_to_string(kernel->radial));
  fprintf(outfile, " width_factor=%g", kernel->width_factor);
  fprintf(outfile, " two_squared_width=%g\n", kernel->two_squared_width);

  if (print_time) {
    fprintf(outfile, "# host=%s", hostname());
    fprintf(outfile, " date=%s\n", date_and_time());
  }
}

/****************************************************************************
 * Main procedure
 ****************************************************************************/
#ifdef MAIN
#include "cmdline.h"
#include <time.h>

VERBOSE_T verbosity = NORMAL_VERBOSE;

int main(int argc, char *argv[])
{
  // Command line options.
  char*     learned_filename = NULL; // Training set weights or eigenvectors.
  FILE*     learned_file = NULL;
  BOOLEAN_T format_line = FALSE;     // Use format line in RDB files?
  BOOLEAN_T kernel_output = FALSE;   // Output kernel matrix and stop?
  BOOLEAN_T print_time = TRUE;       // Print timing data to output header.

  // Data structures.
  KERNEL_T*      kernel = allocate_kernel(FALSE);  // Kernel function.
  RDB_MATRIX_T*  learned_matrix = NULL;  // Weights / eigenvectors of training
  ARRAY_T*       classifications = NULL; // Predicted classifications.
  ARRAY_T*       discriminants = NULL;   // Discriminants of test set.
  RDB_MATRIX_T*  output_matrix = NULL;   // All output.

  // Get the command line arguments.
  DO_STANDARD_COMMAND_LINE
    (3,
     DATA_OPTN(1, train, <filename> (required), 
	       kernel->train_filename = _OPTION_);
     DATA_OPTN(1, learned, <filename> (required), learned_filename = _OPTION_);
     DATA_OPTN(1, test, <filename> (required),
	       kernel->test_filename = _OPTION_);
     DATA_OPTN(1, selftrain, <filename>, 
	       kernel->self_train_filename = _OPTION_);
     DATA_OPTN(1, selftest, <filename>, 
	       kernel->self_test_filename = _OPTION_);
     FLAG_OPTN(1, rdb, format_line = TRUE);
     FLAG_OPTN(1, kernelout, kernel_output = TRUE);
     FLAG_OPTN(1, notime, print_time = FALSE);
     DATA_OPTN(1, verbose, 1|2|3|4|5 (default=2), 
	       verbosity = (VERBOSE_T)atoi(_OPTION_));
     );

  // Print the process ID.
  if (verbosity >= NORMAL_VERBOSE) {
    fprintf(stderr, "process_id=%d\n", (int)(getpid()));
  }

  // Make sure we got the required files.
  if (kernel->test_filename == NULL) {
    die("No test file given.");
  }
  if (learned_filename == NULL) {
    if (CLASSIFY) {
      die("No training set weights given.\n");
    } else {
      die("No training set eigenvectors given.\n");
    }
  }

  // Read the kernel parameters from the header of the learned file.
  read_kernel_parameters(learned_filename, kernel);

  // Check for more files.
  if ((kernel->train_filename == NULL) && (!kernel->matrix_from_file)) {
    die("No training file given.");
  }
  if ((kernel->matrix_from_file) && (kernel->normalize || kernel->radial)) {
    if (kernel->self_train_filename == NULL) {
      die("No self-train file given.");
    }
    if (kernel->self_test_filename == NULL) {
      die("No self-test file given.");
    }
  }

  // Read the associated weights or eigenvectors.
  if (open_file(learned_filename, "r", TRUE, "learned", "learned values",
		&learned_file) == 0)
    exit(1);
  learned_matrix = read_rdb_matrix(format_line, NULL, learned_file);

  // Read the data files.
  read_data_files(format_line, kernel);

  // Compute the kernel;
  compute_base_kernel(kernel, NULL, NULL);

  // Turbo-ize the kernel matrix.
  transform_kernel_matrix(FALSE,
			  kernel, 
			  NULL,
			  kernel->self_train,
			  kernel->self_test,
			  kernel->kernel_matrix);

  // Stop here if requested.
  if (kernel_output) {
    print_rdb_matrix(kernel->kernel_matrix_rdb, format_line, 8, 6, stdout);
    exit(0);
  }

  if (CLASSIFY) {
    ARRAY_T* matrix_column;

    // Classify the test data.
    if (verbosity >= NORMAL_VERBOSE) {
      fprintf(stderr, "Classifying the test data.\n");
    }
    matrix_column = get_matrix_column(1, get_raw_matrix(learned_matrix));
    classify_list(get_num_rows(kernel->test_matrix),
		  matrix_column,
		  kernel->kernel_matrix,
		  &classifications,
		  &discriminants);
    free_array(matrix_column);

    // Create the output matrix.
    output_matrix = allocate_rdb_matrix(get_num_rows(kernel->test_matrix),
					2, NULL);
    set_corner_string(get_corner_string(kernel->kernel_matrix_rdb),
		      output_matrix);
    set_row_names(get_row_names(kernel->kernel_matrix_rdb), output_matrix);
    add_rdb_column("classification", classifications, output_matrix);
    add_rdb_column("discriminant", discriminants, output_matrix);

  } else {

    // Allocate space for the projected data.
    output_matrix
      = allocate_rdb_matrix(get_num_rows(kernel->test_matrix),
			    get_num_cols(get_raw_matrix(learned_matrix)),
			    NULL);
    set_corner_string(get_corner_string(kernel->kernel_matrix_rdb),
		      output_matrix);
    set_row_names(get_row_names(kernel->test_matrix_rdb), output_matrix);
    set_col_names(get_col_names(learned_matrix), output_matrix);

    // Project the data onto the eigenvectors.
    if (verbosity >= NORMAL_VERBOSE) {
      fprintf(stderr, "Projecting the test data.\n");
    }
    project_data(kernel->kernel_matrix,
		 get_raw_matrix(learned_matrix),
		 get_raw_matrix(output_matrix));

  }

  // Print the results to stdout.
  print_output_header(learned_filename,
		      kernel,
		      print_time,
		      stdout);
  print_rdb_matrix(output_matrix, format_line, 6, 4, stdout);
  
#ifdef DEBUG
  // Free dynamic memory.
  free_kernel(kernel);
  free_rdb_matrix(learned_matrix);
  free_array(classifications);
  free_array(discriminants);
  free_rdb_matrix(output_matrix);
#endif
  return(0);
}
#endif


/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 2
 * End:
 */
 
